import _ from 'lodash'
import dayjs from 'dayjs';

import React from 'react';
import { Typography } from 'antd';

import BigNumber from "bignumber.js";

import SparkChannel from '../ipc/sparkchannel'
import { DayUtil, SheetUtil } from '../util/utils'
import { TableType, DataType } from './common'
import intl from '../intl'
import Fetcher from './fetch'

const { Text } = Typography;

function formatPointDesc(point, data_type) {
  let name = `${point.station_desc}/${point.bay_desc}/${point.device_desc}/${point.point_desc}`;
  if (data_type) {
    const dataType = DataType.getByValue(data_type);
    name = name + ` (${dataType.label})`;
  }
  return name;
}

function formatProps(props) {
  const els = [];
  if (!_.isNil(props.portrait))
    els.push((<React.Fragment key='portrait'>
      <dt>{intl.messages.label_portrait}</dt>
      <dd>{props.portrait ? '↓' : '→'}</dd>
    </React.Fragment>));
  if (!_.isNil(props.ratio))
    els.push((<React.Fragment key='ratio'>
      <dt>{intl.messages.label_ratio}</dt>
      <dd>{props.ratio.toString()}</dd>
    </React.Fragment>));
  if (!_.isNil(props.zero))
    els.push((<React.Fragment key='zero'>
      <dt>{intl.messages.label_zero}</dt>
      <dd>{props.zero.toString()}</dd>
    </React.Fragment>));
  if (!_.isNil(props.diff))
    els.push((<React.Fragment key='diff'>
      <dt>{intl.messages.label_diff}</dt>
      <dd>{props.diff.toString()}</dd>
    </React.Fragment>));
  if (!_.isNil(props.begin_delta) || !_.isNil(props.end_delta)) {
    const timeDelta = DayUtil.formatTimeDelta(props.begin_delta) + ' - ' + DayUtil.formatTimeDelta(props.end_delta);
    els.push((<React.Fragment key='delta'>
      <dt>{intl.messages.label_delta_time}</dt>
      <dd>{timeDelta}</dd>
    </React.Fragment>));
  }
  return els;
}


class FillHolder {
  constructor(props) {
    this._fields = this.getFields();
    for (const f of this._fields) {
      this[f] = props[f];
    }
  }

  getFields() {
    return [];
  }

  toString() {
    return JSON.stringify(this, this._fields);
  }

  getDesc() {
    return Promise.resolve('Abstract holder');
  }

  async renderHolder(renderCtx, cellData, fullJson) {
    // all props of hoder are filled.
    // pls extend cell here.
  }

  // 填充数据有两种数据类型：时间和数字
  processValue(cellData, value) {
    const v = cellData.v;

    if (value) {
      if (value instanceof Date) {
        value = DayUtil.dateToDays(value);
      } else {
        value = Number(value);
        if (_.isFinite(this.ratio)) {
          // js浮点数相乘有误差
          const v = new BigNumber(value);
          const r = new BigNumber(this.ratio);
          value = v.multipliedBy(r).toNumber();
        }
      }
    } else {
      if (this.zero) {
        value = 0;
      } else {
        value = '-';
      }
    }

    delete v.m;
    delete v.ps;
    v.v = value;
    return v;
  }
}

class EnvFillHoder extends FillHolder {
  constructor(props) {
    super(props);
    this.type = 'ENV';
    this.title = '变量填充';
  }

  getFields() {
    return ['type', 'name'];
  }

  getDesc() {
    const desc = (<Text strong>{this.name}</Text>)
    return Promise.resolve(desc);
  }

  async renderHolder(renderCtx, cellData, fullJson) {
    // console.log('renderHolder', renderCtx, sheet, cellData, fullJson)
    const envs = renderCtx.envs;
    const value = envs[this.name]; // :string
    const { r, c } = cellData;
    renderCtx.setCellValue(r, c, value);
  }
}


class RealFillHoder extends FillHolder {
  constructor(props) {
    super(props);
    this.type = 'REAL';
    this.title = '实时填充';
  }

  getFields() {
    return ['type', 'id', 'table_type', 'ratio', 'zero'];
  }

  async getDesc() {
    const { ratio, zero } = this;
    const channel = await SparkChannel();
    const table_type = TableType.getTableTypeId(this.table_type);
    const resp = await channel.request_get_point_desc({ point_id: this.id, data_type: table_type });
    const point = resp.data.point;
    return (<>
      <Text strong>{formatPointDesc(point)}</Text>
      <dl>
        {formatProps({ ratio, zero })}
      </dl>
    </>);
  }

  async renderHolder(renderCtx, cellData, fullJson) {
    const value = await renderCtx.fetcher.readReal(fullJson);
    const { r, c } = cellData;
    renderCtx.setCellValue(r, c, this.processValue(cellData, value));
  }
}

class TimeFillHoder extends FillHolder {
  constructor(props) {
    super(props);
    this.type = 'TIME';
    this.title = '时间填充';
  }

  getFields() {
    return ['type', 'diff', 'portrait', 'begin_delta', 'end_delta',
      'id', 'table_type', 'data_type'];
  }

  getDesc() {
    const { portrait, diff, begin_delta, end_delta } = this;
    const desc = (<>
      <dl>
        {formatProps({ portrait, diff, begin_delta, end_delta })}
      </dl>
    </>);
    return Promise.resolve(desc);
  }

  async renderHolder(renderCtx, cellData, fullJson) {
    const value = await renderCtx.fetcher.readSeq(fullJson);
    const { r, c } = cellData;

    const num = value.length;
    if (num > 1) {
      if (this.portrait) {
        renderCtx.expandRows(r + 1, num - 1);
      } else {
        renderCtx.expandCols(c + 1, num - 1);
      }
    }

    // fill first
    let v1 = value[0] && value[0].time;
    renderCtx.setCellValue(r, c, this.processValue(cellData, v1));
    // fill others
    for (let i = 1; i < num; i++) {
      v1 = value[i].time;
      if (this.portrait) {
        renderCtx.setCellValue(r + i, c, this.processValue(cellData, v1));
      } else {
        renderCtx.setCellValue(r, c + i, this.processValue(cellData, v1));
      }
    }
  }
}

class SeqFillHoder extends FillHolder {
  constructor(props) {
    super(props);
    this.type = 'SEQ';
    this.title = '序列填充';
  }

  getFields() {
    return ['type', 'diff', 'portrait', 'begin_delta', 'end_delta',
      'id', 'table_type', 'data_type', 'ratio', 'zero'];
  }

  async getDesc() {
    const channel = await SparkChannel();
    const table_type = TableType.getTableTypeId(this.table_type);
    const resp = await channel.request_get_point_desc({ point_id: this.id, data_type: table_type });
    const point = resp.data.point;
    return (<>
      <Text strong>{formatPointDesc(point, this.data_type)}</Text>
      <dl>
        {formatProps(this)}
      </dl>
    </>);
  }

  async renderHolder(renderCtx, cellData, fullJson) {
    const value = await renderCtx.fetcher.readSeq(fullJson);
    const { r, c } = cellData;

    const num = value.length;
    if (num > 1) {
      if (this.portrait) {
        renderCtx.expandRows(r + 1, num - 1);
      } else {
        renderCtx.expandCols(c + 1, num - 1);
      }
    }

    // fill first
    let v1 = value[0] && value[0].value;
    renderCtx.setCellValue(r, c, this.processValue(cellData, v1));
    // fill others
    for (let i = 1; i < num; i++) {
      v1 = value[i].value;
      if (this.portrait) {
        renderCtx.setCellValue(r + i, c, this.processValue(cellData, v1));
      } else {
        renderCtx.setCellValue(r, c + i, this.processValue(cellData, v1));
      }
    }
  }
}

class StatFillHoder extends FillHolder {
  constructor(props) {
    super(props);
    this.type = 'STAT';
    this.title = '统计填充';

    if (this.data_type === 'VOLT_ELIG'
      || this.data_type === 'OVER_TIME_HIGH'
      || this.data_type === 'OVER_TIME_LOW') {
      this.ratio = undefined;
    }
  }

  getFields() {
    return ['type', 'diff', 'begin_delta', 'end_delta',
      'id', 'table_type', 'data_type', 'ratio', 'zero'];
  }

  async getDesc() {
    const channel = await SparkChannel();
    const table_type = TableType.getTableTypeId(this.table_type);
    const resp = await channel.request_get_point_desc({ point_id: this.id, data_type: table_type });
    const point = resp.data.point;
    return (<>
      <Text strong>{formatPointDesc(point, this.data_type)}</Text>
      <dl>
        {formatProps(this)}
      </dl>
    </>);
  }

  async renderHolder(renderCtx, cellData, fullJson) {
    const value = await renderCtx.fetcher.readStat(fullJson);
    const { r, c } = cellData;
    renderCtx.setCellValue(r, c, this.processValue(cellData, value));
  }
}

class StatTimeFillHoder extends FillHolder {
  constructor(props) {
    super(props);
    this.type = 'STAT_TIME';

    const dataType = DataType.getByValue(this.data_type);
    this.title = dataType.label + '发生时间';
  }

  getFields() {
    return ['type', 'diff', 'begin_delta', 'end_delta',
      'id', 'table_type', 'data_type'];
  }

  async getDesc() {
    const channel = await SparkChannel();
    const table_type = TableType.getTableTypeId(this.table_type);
    const resp = await channel.request_get_point_desc({ point_id: this.id, data_type: table_type });
    const point = resp.data.point;
    return (<>
      <Text strong>{formatPointDesc(point, this.data_type)}</Text>
      <dl>
        {formatProps(this)}
      </dl>
    </>);
  }

  async renderHolder(renderCtx, cellData, fullJson) {
    const value = await renderCtx.fetcher.readStat(fullJson);
    const { r, c } = cellData;
    if (value) {
      renderCtx.setCellValue(r, c, this.processValue(cellData, value));
    } else {
      const v = cellData.v;
      delete v.m;
      delete v.ps;
      v.v = '';
      renderCtx.setCellValue(r, c, v);
    }
  }
}

const ALL_HOLDER_TYPE = {
  ENV: EnvFillHoder,
  REAL: RealFillHoder,
  TIME: TimeFillHoder,
  SEQ: SeqFillHoder,
  STAT: StatFillHoder,
  STAT_TIME: StatTimeFillHoder,
};


class FillHepler {
  constructor(luckysheet, bookType, portrait = true) {
    this.luckysheet = luckysheet;
    this.bookType = bookType;
    this.portrait = portrait;

    const ranges = luckysheet.getRangeWithFlatten();
    const range = ranges[0];
    const { r, c } = range;
    this.start_row = r;
    this.start_col = c;
  }

  getTimeFormat(reportType) {
    if (reportType === 'YEAR') {
      return "yyyy";
    } else if (reportType === 'MONTH') {
      return "yyyy-MM";
    } else if (reportType === 'WEEK') {
      return "yyyy-MM-dd";
    } else if (reportType === 'DAY') {
      return "yyyy-MM-dd";
    } if (reportType === 'HOUR') {
      return "yyyy-MM-dd hh:mm";
    }
  }

  getSubReportType() {
    if (this.bookType === 'YEAR') {
      return "MONTH";
    } else if (this.bookType === 'MONTH') {
      return "DAY";
    } else if (this.bookType === 'WEEK') {
      return "DAY";
    } else if (this.bookType === 'DAY') {
      return "HOUR";
    }
  }

  setCellValue(r, c, value, setting) {
    let rr, cc;
    if (this.portrait) {
      rr = this.start_row + r;
      cc = this.start_col + c;
    } else {
      rr = this.start_row + c;
      cc = this.start_col + r;
    }

    if (_.isString(value)) {
      this.luckysheet.setCellValue(rr, cc, value, setting);
    } else { // value is instance of FillHolder
      const text = value.toString();
      // const comment = value.getDesc();
      const vv = {
        "v": text,
        "m": text,
        // "ps": {
        //     "value": comment,
        // },
      };

      if (value.type === 'TIME') {
        // 时间格式
        const reportType = this.getSubReportType();
        const fmt = this.getTimeFormat(reportType);

        if (fmt) {
          vv["ct"] = { "fa": fmt, "t": "d" };
        }
      } else if (value.type === 'STAT_TIME') {
        vv["ct"] = { "fa": "yyyy-MM-dd hh:mm:ss", "t": "d" };
      } else if (value.type === 'REQL' || value.type === 'SEQ') {
        // 数字格式
        // vv["ct"] = { "fa": 'General', "t": "g" };
      } else if (value.type === 'STAT') {
        if (value.data_type === 'VOLT_ELIG') {
          vv["ct"] = { "fa": '0.00%', "t": "n" };
        } else {
          // vv["ct"] = { "fa": 'General', "t": "g" };
        }
      }

      this.luckysheet.setCellValue(rr, cc, vv, setting);
    }
  }
}

function parseHolder(value) {
  let cellJson;
  let holder;
  try {
    cellJson = JSON.parse(value);
    // cellJson.ratio = 0.001;
    if (_.isPlainObject(cellJson)) {
      const newFn = ALL_HOLDER_TYPE[cellJson.type];
      if (newFn) {
        holder = new newFn(cellJson);
      }

    }
  } catch {
    // ignored
  }
  return { holder, cellJson };
}


class RenderHelper {
  constructor(luckysheet, content, userValues) {
    this.luckysheet = luckysheet;
    this.content = content;
    this.userValues = userValues;

    this.buildPureCellData(content);

    var now = dayjs();
    this.renderDate = now;
    this.envs = {
      REPORT_DATE: now.format('YYYY-MM-DD'),
      REPORT_DATETIME: now.format('YYYY-MM-DD HH:mm:ss'),
      REPORT_USER: userValues && userValues.report_user,
    };
    this.fetcher = new Fetcher();

    // sheet states
    this.sheet = null;
    this.celldata = null;
    this.newRows = {};
    this.newCols = {};
  }

  /**
   * 过滤掉holder的单元格数据
   */
  buildPureCellData(content) {
    const data = content.data;
    for (const sheet of data) {
      sheet.origin_celldata = _.cloneDeep(sheet.celldata);
      const pure_celldata = sheet.celldata.filter(cell => {
        const { v } = cell;
        const ct = v.ct;
        let value = v.v;
        if (SheetUtil.isInlineStringCT(ct)) {
          value = SheetUtil.getInlineStringNoStyle(ct);
        }
        const { holder } = parseHolder(value);
        return !holder;
      });
      sheet.celldata = pure_celldata;
    }
  }

  getPureContent() {
    return this.content;
  }

  setCellValue(r, c, value) {
    const setting = { order: this.sheet.order };
    // console.log('setCellValue', r, c, value);
    this.luckysheet.setCellValue(r, c, value, setting);
  }

  expand_conditionformat(type, start, num) {
    let CFarr = this.sheet.luckysheet_conditionformat_save;
    let newCFarr = [];
    if (CFarr != null && CFarr.length > 0) {
      for (let i = 0; i < CFarr.length; i++) {
        let cf_range = CFarr[i].cellrange;
        let cf_new_range = [];

        for (let j = 0; j < cf_range.length; j++) {
          let CFr1 = cf_range[j].row[0],
            CFr2 = cf_range[j].row[1],
            CFc1 = cf_range[j].column[0],
            CFc2 = cf_range[j].column[1];

          if (type === "row") {
            if (CFr2 === start - 1) {
              CFr2 += num;
            }
          }
          else if (type === "column") {
            if (CFc2 === start - 1) {
              CFc2 += num;
            }
          }

          cf_new_range.push({ "row": [CFr1, CFr2], "column": [CFc1, CFc2] });
        }

        let cf = { ...CFarr[i] };
        cf.cellrange = cf_new_range;

        newCFarr.push(cf);
      }
    }

    // 直接更新luckysheet_conditionformat_save，在后续操作中会刷新工作簿，条件格式配置信息就会生效
    this.sheet.luckysheet_conditionformat_save = newCFarr;
    // console.log('expand_conditionformat', type, start, num, newCFarr);
  }

  // 在第row行插入num个新的空白行
  expandRows(row, num) {
    const setting = { order: this.sheet.order };
    for (let i = 0; i < num; i++) {
      const r = row + i;
      if (r in this.newRows) {
        continue;
      }
      this.luckysheet.insertRow(r, setting);
      this.newRows[r] = true;

      // 处理所有的celldata
      for (const cell of this.celldata) {
        if (cell.r >= r) {
          cell.r += 1;
        }
      }

      this.expand_conditionformat('row', r, 1);
    }
  }

  expandCols(col, num) {
    const setting = { order: this.sheet.order };
    for (let i = 0; i < num; i++) {
      const c = col + i;
      if (c in this.newCols) {
        continue;
      }
      this.luckysheet.insertColumn(c, setting);
      this.newCols[c] = true;

      // 处理所有的celldata
      for (const cell of this.celldata) {
        if (cell.c >= c) {
          cell.c += 1;
        }
      }

      this.expand_conditionformat('column', c, 1);
    }
  }

  async render() {
    const data = this.content.data;
    for (const sheet of data) {
      this.sheet = sheet;
      this.celldata = _.cloneDeep(sheet.origin_celldata);
      this.newRows = {};
      this.newCols = {};
      await this.renderSheet();
    }
  }

  async renderSheet() {
    for (const cell of this.celldata) {
      const { r, c, v } = cell;
      const ct = v.ct;
      let value = v.v;
      if (SheetUtil.isInlineStringCT(ct)) {
        value = SheetUtil.getInlineStringNoStyle(ct);
        v.ct = { "fa": "General", "t": "g" };
      }
      const { holder, cellJson } = parseHolder(value);
      if (!holder) {
        continue;
      }

      const fullJson = { ...this.content.config, ...cellJson, ...this.userValues };
      try {
        console.log('render holder', holder, fullJson);
        await holder.renderHolder(this, cell, fullJson);
      } catch (err) {
        console.error('render error', err);
        this.setCellValue(r, c, err + ': ' + value);
      }
    }
  }
}

export {
  EnvFillHoder,
  RealFillHoder,
  TimeFillHoder,
  SeqFillHoder,
  StatFillHoder,
  StatTimeFillHoder,
  FillHepler,
  RenderHelper,
  parseHolder,
}
